﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.IO;
using System.Threading;
using log4net;
using Engine.Plot;
using System.Runtime.Serialization.Formatters.Binary;

namespace Engine
{
    /// <summary>
    /// Class that studied behaviour of lattice with non-linear elements
    /// </summary>
    public class NonLinearResearchEngine
    {
        
        /// <summary>
        /// Log for this class
        /// </summary>
        private static readonly ILog Log = LogManager.GetLogger(typeof(NonLinearResearchEngine));

        /// <summary>
        /// x size of underlying lattice
        /// </summary>
        private readonly int _xLenght;

        /// <summary>
        /// y size of underlying lattice
        /// </summary>
        private readonly int _yLenght;

        /// <summary>
        /// Number of iteration cycles before we find average values.
        /// </summary>
        private readonly int _numberOfCycles;

        /// <summary>
        /// Underlying lattice. Properties of which we studying.
        /// </summary>
        private Square _lattice;

        /// <summary>
        /// On calculating conductivity for lattice
        /// </summary>
        public event EventHandler<Plot.PlotPointFindedEventArgs> PlotPointFindedEvent;

        /// <summary>
        /// When we end execution of this task
        /// </summary>
        public event EventHandler EndEvent;
        
        /// <summary>
        /// Base Constructor
        /// </summary>
        public NonLinearResearchEngine(int xLenght, int yLenght, int numberOfCycles)
        {
            _xLenght = xLenght;
            _yLenght = yLenght;
            _numberOfCycles = numberOfCycles;
        }
        
        /// <summary>
        /// Get current lattice. This need for save mathod
        /// Because we need to know lattice id and other lattice parameters.
        /// </summary>
        public Lattice CurrentLattice 
        {
            get { return _lattice; }
        }

        /// <summary>
        /// Serialize lattice to specified file
        /// </summary>
        /// <param name="aFilePath">path to file</param>
        public void SerializeLatticeToFile(string aFilePath)
        {
            if (_lattice != null)
            {
                //To ensure that lattice is not modified outside of code scope..
                lock(_lattice)
                {
                    IFormatter formatter = new BinaryFormatter();
                    Stream stream = new FileStream( aFilePath,
                                                    FileMode.OpenOrCreate,
                                                    FileAccess.Write, 
                                                    FileShare.None);
                    formatter.Serialize( stream, _lattice );
                    stream.Close();
                }
            }
            else
            {
                if(Log.IsErrorEnabled)
                {
                    Log.Error("Попытка сохранить состояние решетки до того как она была создана или загружена.");
                }
                throw new NullReferenceException("Attempt to serialize lattice before we create or load one.");
            }
        }

        /// <summary>
        /// Deserialize lattice from specified file
        /// </summary>
        /// <param name="aFilePath">path to file</param>
        public void DeserializeLatticeFromFile(string aFilePath)
        {
            if (_lattice == null)
            {
                IFormatter formatter = new BinaryFormatter();
                Stream stream = new FileStream(aFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                _lattice = (Square) formatter.Deserialize(stream);
                stream.Close();
            }
            else
            {
                if(Log.IsErrorEnabled)
                {
                    Log.Error("Мы не можем загрузить расчитываемую решетку после того как она была загружена или создана.");
                }
                throw new InvalidOperationException("we can't load lattice after previous loading or creation.");
            }
        }


        /// <summary>
        /// Main work on 
        /// </summary>
        public void StudyStudingDependanceConductivityOnNumberOfElements(CancellationToken cancellationToken)
        {
            try
            {
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Начинаем исследование решетки. ЕЕ параметры - XLenght = " + _xLenght + "; YLenght=" +
                              _yLenght);
                }
                if (_lattice == null)
                {
                    if (Log.IsDebugEnabled)
                    {
                        Log.Debug("Создаем новую решетку");
                    }
                    _lattice = new Square { X = _xLenght, Y = _yLenght , Id = 1 , NonLinearConductance = NonLinearConductivity };
                    //prepare lattice
                    //todo - unify this with constructor
                    _lattice.Reset();
                }
                else
                {
                    if(Log.IsDebugEnabled)
                    {
                        Log.Debug("Используем загруженную решетку");
                    }
                }
                for (int cycleIndex = 0; cycleIndex < _numberOfCycles; cycleIndex++ )
                {
                    if(cycleIndex > 0)
                    {
                        _lattice = new Square { X = _xLenght, Y = _yLenght , Id = _lattice.Id + 1 };
                        //prepare lattice
                        //todo - unify this with constructor
                        _lattice.Reset();
                    }
                    bool isConduct = true;
                    //show how many iterations we do before calculating conductivity
                    int step = 0;
                    while (isConduct)
                    {
                        lock (_lattice)
                        {
                            if (cancellationToken.IsCancellationRequested)
                            {
                                //go out from execution...
                                cancellationToken.ThrowIfCancellationRequested();
                            }
                            _lattice.CutBond();
                            if (_lattice.XBonds < 1.0 && _lattice.WorkingBonds % 10 == 0)
                            {
                                if (Log.IsDebugEnabled)
                                {
                                    Log.Debug("Начинаем расчет проводимости решетки");
                                }
                                
                                double conductance = _lattice.Conductance;
                                if (conductance < 0.00000001) isConduct = false;
                                double x = _lattice.XBonds;
                                Log.Info("Нашли новое значение для которого x = " + x + "; conductance=" + conductance);
                                PlotPoint plotPoint = new PlotPoint(x, conductance, _lattice.Id, ResultType.STUDIED);
                                plotPoint.ElementConductance = 0;
                                //calculate conductance for normal lattice.
                                _lattice.BondConductance = 1;
                                double normConductance = _lattice.Conductance;
                                PlotPoint normPlotPoint = new PlotPoint(x, normConductance, _lattice.Id, ResultType.NORM);
                                normPlotPoint.ElementConductance = 1;
                                PlotPointFindedEventArgs e = new PlotPointFindedEventArgs(plotPoint);
                                e.NormPoint = normPlotPoint;                                
                                //notify that we getted this element
                                PlotPointFindedEvent(this, e);
                                step = 0;
                            }
                        }
                        //aditional check 
                        //IsConduct = square.IsConduct();
                    }
                }
            }
            catch(Exception e)
            {
                if(_lattice != null)
                {
                    if(Log.IsInfoEnabled)
                    {
                        Log.Info("Сохраняем состояние решетки. Операция остановлена вследствии", e);
                    }
                    SerializeLatticeToFile("recovery.bin");
                }
                throw e;
            }
            finally
            {
                _lattice = null;
                //Raise end event
                this.EndEvent(this, new EventArgs());
            }
        }

        /// <summary>
        /// Represent Conductivity of non linear element
        /// </summary>
        /// <param name="appliedVoltage">voltage applied to this element</param>
        /// <returns>current that flows throw this element</returns>
        private double NonLinearConductivity(double appliedVoltage) {
            return 10  * (Math.Exp( (1.6E-19 * appliedVoltage)/ (1.3806503E-23 * 300) ) - 1 );
        }


        /// <summary>
        /// Number of step in while we don't need to calculate conductivity
        /// </summary>
        /// <param name="x">number of working bonds</param>
        /// <returns>Number of step in while we don't need to calculate conductivity</returns>
        private int NumberOfStepsBeforeCalculatingCoductivity(int x)
        {
            return 1;
        }

    
    }
}
